/**
 * Copyright (c) iwindplus Technologies Co., Ltd.2024-2030, All rights reserved.
 */

package com.iwindplus.boot.util;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.URLEncodeUtil;
import com.iwindplus.boot.domain.constant.CommonConstant.ExceptionConstant;
import com.iwindplus.boot.domain.constant.CommonConstant.FileConstant;
import jakarta.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.ClassMetadata;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

/**
 * 文件相关工具类.
 *
 * @author zengdegui
 * @since 2021/1/11
 */
@Slf4j
public class FilesUtil extends FileUtil {

    private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();

    private static final MetadataReaderFactory METADATA_READER_FACTORY = new CachingMetadataReaderFactory();

    /**
     * File转MultipartFile.
     *
     * @param file 文件
     * @return MultipartFile
     */
    public static MultipartFile fileToMultipartFile(File file) {
        try {
            return new MockMultipartFile(file.getName(), file.getName(), null, Files.newInputStream(file.toPath()));
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
        return null;
    }

    /**
     * MultipartFile转File.
     *
     * @param multipartFile 文件
     * @return File
     */
    public static File multipartFileToFile(MultipartFile multipartFile) {
        File file = new File(multipartFile.getOriginalFilename());
        try {
            multipartFile.transferTo(file);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
        return file;
    }

    /**
     * MultipartFile转InputStream.
     *
     * @param multipartFile 文件
     * @return InputStream
     */
    public static InputStream getInputStreamByMultipartFile(MultipartFile multipartFile) {
        InputStream inputStream = null;
        try {
            inputStream = multipartFile.getInputStream();
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
        return inputStream;
    }

    /**
     * MultipartFile转byte.
     *
     * @param multipartFile 文件
     * @return byte[]
     */
    public static byte[] getBytes(MultipartFile multipartFile) {
        try {
            return multipartFile.getBytes();
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
        return new byte[0];
    }

    /**
     * File转byte.
     *
     * @param file 文件
     * @return byte[]
     */
    public static byte[] getBytes(File file) {
        try {
            return FileCopyUtils.copyToByteArray(file);
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        }
        return new byte[0];
    }

    /**
     * 下载文件.
     *
     * @param inputStream 输入流（必填）
     * @param fileName    文件名，包含后缀（必填）
     * @param response    响应（必填）
     */
    public static void downloadFile(InputStream inputStream, String fileName, HttpServletResponse response) {
        try (OutputStream out = response.getOutputStream()) {
            setHttpServletResponse(fileName, response);
            FileCopyUtils.copy(inputStream, out);
            response.flushBuffer();
        } catch (IOException ex) {
            log.error(ExceptionConstant.IO_EXCEPTION, ex);
        } finally {
            FilesUtil.closeInputStream(inputStream);
        }
    }

    /**
     * 设置响应头，用于下载.
     *
     * @param fileName 下载文件名（包含后缀）
     * @param response 响应
     */
    public static void setHttpServletResponse(String fileName, HttpServletResponse response) {
        String filename = URLEncodeUtil.encode(fileName, Charset.defaultCharset());
        response.setCharacterEncoding(Charset.defaultCharset().name());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
        response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, new StringBuilder("attachment;filename=").append(filename).toString());
    }

    /**
     * 关闭InputStream流.
     *
     * @param inputStream 输入流
     */
    public static void closeInputStream(InputStream inputStream) {
        if (Objects.nonNull(inputStream)) {
            try {
                inputStream.close();
            } catch (IOException ex) {
                log.error(ExceptionConstant.IO_EXCEPTION, ex);
            }
        }
    }

    /**
     * 关闭outputStream流.
     *
     * @param outputStream 输出流
     */
    public static void closeOutputStream(OutputStream outputStream) {
        if (Objects.nonNull(outputStream)) {
            try {
                outputStream.close();
            } catch (IOException ex) {
                log.error(ExceptionConstant.IO_EXCEPTION, ex);
            }
        }
    }

    /**
     * 根据文件名，获取ContentType.
     *
     * @param fileName 文件名
     * @return String
     */
    public static String getContentType(String fileName) {
        Optional<MediaType> mediaType = MediaTypeFactory.getMediaType(fileName);
        return String.valueOf(mediaType.orElse(MediaType.APPLICATION_OCTET_STREAM));
    }

    /**
     * 扫描指定包路径下的类.
     *
     * @param packagePatterns 包路径，多个逗号分割
     * @param assignableType  指定类型，确定一个类是否是另一个类的子类
     * @return Set<Class < ?>>
     */
    public static Set<Class<?>> scanClasses(String packagePatterns, Class<?> assignableType) {
        Set<Class<?>> classes = new HashSet<>(16);
        try {
            String[] packagePatternArray = StringUtils.tokenizeToStringArray(packagePatterns,
                ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packagePattern : packagePatternArray) {
                final String resourcePath = new StringBuilder(ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX)
                    .append(ClassUtils.convertClassNameToResourcePath(packagePattern))
                    .append(FileConstant.MORE_CLASS).toString();
                Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(resourcePath);
                for (Resource resource : resources) {
                    ClassMetadata classMetadata = METADATA_READER_FACTORY.getMetadataReader(resource).getClassMetadata();
                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
                    if (assignableType == null || assignableType.isAssignableFrom(clazz)) {
                        classes.add(clazz);
                    }
                }
            }
        } catch (Exception ex) {
            log.error(ExceptionConstant.EXCEPTION, ex);
        }
        return classes;
    }
}
